package com.hucsmn.examsystem.common;

import com.hucsmn.examsystem.util.BuildStrings;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * 答题卷。
 */
public class AnswerSheet implements Serializable, Cloneable {
    public static final int DEFAULT_SCORE = 100;
    public static final int DEFAULT_PROBLEMS = 5;
    private static final long serialVersionUID = 5616733108495067918L;
    private static final SimpleDateFormat durationFormat = new SimpleDateFormat("HH:mm:ss");
    private static final SimpleDateFormat submitTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 试卷编号、用户编号、考试时长、提交时间、试卷总分、题目列表。
    private int id;
    private int uid;
    private long duration;
    private long submitTime;
    private int score;
    private Problem[] problems;

    public AnswerSheet(int id, Problem... problems) {
        this(id, DEFAULT_SCORE, problems);
    }

    public AnswerSheet(int id, int score, Problem... problems) {
        if (id < 0) {
            throw new IllegalArgumentException("negative answer sheet id");
        }
        this.id = id;
        if (problems.length > score) {
            throw new IllegalArgumentException("total score is too small");
        }
        this.score = problems.length;
        setProblems(problems);
        setScore(score);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        if (id < 0) {
            throw new IllegalArgumentException("negative answer sheet id");
        }
        this.id = id;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        if (uid < 0) {
            throw new IllegalArgumentException("negative user id");
        }
        this.uid = uid;
    }

    public long getDuration() {
        return duration;
    }

    public void setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("negative duration");
        }
        this.duration = duration;
    }

    public long getSubmitTime() {
        return submitTime;
    }

    public void setSubmitTime(long submitTime) {
        this.submitTime = submitTime;
    }

    public int length() {
        if (problems == null) {
            return 0;
        }
        return problems.length;
    }

    public Problem[] getProblems() {
        return problems;
    }

    public void setProblems(Problem... problems) {
        if (problems == null) {
            throw new NullPointerException();
        }
        if (problems.length == 0) {
            throw new IllegalArgumentException("answer sheet cannot be empty");
        }

        Problem[] newProblems = new Problem[problems.length];
        for (int i = 0; i < problems.length; i++) {
            if (problems[i] == null) {
                throw new NullPointerException();
            }
            try {
                newProblems[i] = problems[i].clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
        if (Arrays.stream(newProblems).anyMatch(Objects::isNull)) {
            throw new NullPointerException();
        }

        int oldLength = length();
        this.problems = newProblems;
        if (newProblems.length != oldLength) {
            setScore(this.score);
        }
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        if (score < problems.length) {
            throw new IllegalArgumentException("total score is too small");
        }
        this.score = score;
        int each = score / problems.length;
        int remain = score % problems.length;
        for (Problem problem : problems) {
            problem.setScore(each);
        }
        if (remain != 0) {
            problems[problems.length - 1].setScore(each + remain);
        }
    }

    public int getFinalScore() {
        return Arrays.stream(problems).mapToInt(Problem::getFinalScore).sum();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AnswerSheet that = (AnswerSheet) o;
        return score == that.score &&
                Arrays.equals(problems, that.problems);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(score);
        result = 31 * result + Arrays.hashCode(problems);
        return result;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        AnswerSheet answerSheet = (AnswerSheet) super.clone();
        Problem[] problems = new Problem[this.problems.length];
        for (int i = 0; i < problems.length; i++) {
            problems[i] = this.problems[i].clone();
        }
        answerSheet.setProblems(problems);
        return answerSheet;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("考卷（满分 ").append(score).append("）").append(BuildStrings.NEWLINE);
        for (int i = 0; i < problems.length; i++) {
            sb.append(i + 1).append(". ").append(problems[i]);
        }
        return sb.toString();
    }

    public String detailedToString() {
        final StringBuilder sb = new StringBuilder();

        sb
                .append("考卷（满分 ").append(score)
                .append("，得分 ").append(getFinalScore())
                .append("，日期 ").append(submitTimeFormat.format(new Date(submitTime * 1000L)))
                .append("，时长 ").append(durationFormat.format(new Date(duration * 1000L)))
                .append("）").append(BuildStrings.NEWLINE);

        for (int i = 0; i < problems.length; i++) {
            sb.append(i + 1).append(". ").append(problems[i].detailedToString());
        }

        return sb.toString();
    }
}
